package com.fishbone.file.minio;

import io.minio.MinioClient;
import io.minio.PutObjectOptions;
import io.minio.ServerSideEncryption;
import io.minio.errors.*;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName HssMinClient
 * @Author huyaxi
 * @DATE 2020/4/21 16:31
 */
@Slf4j
public class HssMinClient extends MinioClient {
    public HssMinClient(String endpoint) throws InvalidEndpointException, InvalidPortException {
        super(endpoint);
    }

    public HssMinClient(URL url) throws InvalidEndpointException, InvalidPortException {
        super(url);
    }

    public HssMinClient(HttpUrl url) throws InvalidEndpointException, InvalidPortException {
        super(url);
    }

    public HssMinClient(String endpoint, String accessKey, String secretKey) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, accessKey, secretKey);
    }

    public HssMinClient(String endpoint, String accessKey, String secretKey, String region) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, accessKey, secretKey, region);
    }

    public HssMinClient(URL url, String accessKey, String secretKey) throws InvalidEndpointException, InvalidPortException {
        super(url, accessKey, secretKey);
    }

    public HssMinClient(HttpUrl url, String accessKey, String secretKey) throws InvalidEndpointException, InvalidPortException {
        super(url, accessKey, secretKey);
    }

    public HssMinClient(String endpoint, int port, String accessKey, String secretKey) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, port, accessKey, secretKey);
    }

    public HssMinClient(String endpoint, String accessKey, String secretKey, boolean secure) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, accessKey, secretKey, secure);
    }

    public HssMinClient(String endpoint, int port, String accessKey, String secretKey, boolean secure) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, port, accessKey, secretKey, secure);
    }

    public HssMinClient(String endpoint, int port, String accessKey, String secretKey, String region, boolean secure) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, port, accessKey, secretKey, region, secure);
    }

    public HssMinClient(String endpoint, int port, String accessKey, String secretKey, String region, boolean secure, OkHttpClient httpClient) throws InvalidEndpointException, InvalidPortException {
        super(endpoint, port, accessKey, secretKey, region, secure, httpClient);
    }


    /**
     * 并发分片上传
     *
     * @param bucketName
     * @param objectName
     * @param options
     * @param data
     * @param service
     * @throws ErrorResponseException
     * @throws IllegalArgumentException
     * @throws InsufficientDataException
     * @throws InternalException
     * @throws InvalidBucketNameException
     * @throws InvalidKeyException
     * @throws InvalidResponseException
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws XmlParserException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public void putObject(String bucketName, String objectName, PutObjectOptions options, Object data, ExecutorService service) throws ErrorResponseException, IllegalArgumentException, InsufficientDataException,
            InternalException, InvalidBucketNameException, InvalidKeyException,
            InvalidResponseException, IOException, NoSuchAlgorithmException, XmlParserException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        Map<String, String> headerMap = new HashMap<>(16);

        if (options.headers() != null) {
            headerMap.putAll(options.headers());
        }

        if (options.sse() != null) {
            headerMap.putAll(options.sse().headers());
        }

        headerMap.put("Content-Type", options.contentType());

        // initiate new multipart upload.
        String uploadId = createMultipartUpload(bucketName, objectName, headerMap);
        long uploadedSize = 0L;
        int partCount = options.partCount();
        Part[] totalParts = new Part[PutObjectOptions.MAX_MULTIPART_COUNT];

        try {
            for (int partNumber = 1; partNumber <= partCount || partCount < 0; partNumber++) {
                long availableSize = options.partSize();
                if (partCount > 0) {
                    if (partNumber == partCount) {
                        availableSize = options.objectSize() - uploadedSize;
                    }
                } else {
                    availableSize = getAvailableSize(data, options.partSize() + 1);
                    // If availableSize is less or equal to options.partSize(), then we have reached last
                    // part.
                    if (availableSize <= options.partSize()) {
                        partCount = partNumber;
                    } else {
                        availableSize = options.partSize();
                    }
                }
                Map<String, String> ssecHeaders = null;
                // set encryption headers in the case of SSE-C.
                if (options.sse() != null && options.sse().type() == ServerSideEncryption.Type.SSE_C) {
                    ssecHeaders = options.sse().headers();
                }
                int finalPartNumber = partNumber;
                long finalAvailableSize = availableSize;
                Map<String, String> finalSsecHeaders = ssecHeaders;
                service.execute(() -> {
                    try {
                        String etag = uploadPart(bucketName, objectName, data, (int) finalAvailableSize, uploadId, finalPartNumber, finalSsecHeaders);
                        totalParts[finalPartNumber - 1] = new Part(finalPartNumber, etag);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
                uploadedSize += availableSize;
            }
            // 等待上传完成
            service.shutdown();
            while (!service.isTerminated()) {
                try {
                    service.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            completeMultipartUpload(bucketName, objectName, uploadId, totalParts);
        } catch (RuntimeException e) {
            abortMultipartUpload(bucketName, objectName, uploadId);
            throw e;
        } catch (Exception e) {
            abortMultipartUpload(bucketName, objectName, uploadId);
            throw e;
        }
    }


    public String initMultipartUpload(String bucketName, String objectName, Map<String, String> headerMap) {
        try {
            return createMultipartUpload(bucketName, objectName, headerMap);
        } catch (MinioException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
            log.error("server error:{},cause:{}", e.getMessage(), e.getCause());
            return null;
        }
    }


    /**
     * @param bucketName
     * @param objectName
     * @param data
     * @param length
     * @param uploadId
     * @param partNumber
     * @param headerMap
     * @return
     */
    public String uploadPartFile(String bucketName, String objectName, Object data, int length, String uploadId, int partNumber, Map<String, String> headerMap) {
        try {
            return uploadPart(bucketName, objectName, data, length, uploadId, partNumber, headerMap);
        } catch (MinioException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
            log.error("server error:{},cause:{}", e.getMessage(), e.getCause());
            return null;
        }
    }


    public void completeMultipartUploadFile(String bucketName, String objectName, String uploadId, Part[] parts) {
        try {
            completeMultipartUpload(bucketName, objectName, uploadId, parts);
        } catch (Exception e) {
            log.error("server error:{},cause:{}", e.getMessage(), e.getCause());
        }
    }


    /**
     * 检查sse
     *
     * @param sse
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private void checkWriteRequestSse(ServerSideEncryption sse) throws NoSuchFieldException, IllegalAccessException {
        Field field = MinioClient.class.getDeclaredField("baseUrl");
        field.setAccessible(true);
        HttpUrl httpUrl = (HttpUrl) field.get(this);
        if (sse.type().requiresTls() && !httpUrl.isHttps()) {
            throw new IllegalArgumentException(
                    sse.type().name() + " operations must be performed over a secure connection.");
        }
    }


    /**
     * @param data
     * @param expectedReadSize
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private long getAvailableSize(Object data, long expectedReadSize) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = MinioClient.class.getDeclaredMethod("getAvailableSize", Object.class, long.class);
        method.setAccessible(true);
        return (long) method.invoke(this, data, expectedReadSize);
    }
}
